package cz.matasek.dbLogger.gui;

import cz.matasek.dbLogger.model.Column;
import cz.matasek.dbLogger.model.Database;
import cz.matasek.dbLogger.model.Table;
import cz.matasek.dbLogger.model.TriggeringEvents;
import cz.matasek.dbLogger.model.TriggeringEvents.Event;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractListModel;
import javax.swing.table.AbstractTableModel;

/**
 * The dialog provides form for creating automatical logging triggers.
 *
 * @author Luboš Matásek
 */
public class LoggingSettingsDialog extends javax.swing.JDialog {

    /**
     * Serial version UID.
     */
    private static final long serialVersionUID = 4751147446434738656L;

    /**
     * The class provides list model for listing columns of a table.
     */
    private class ColumnsListModel extends AbstractListModel {

        /**
         * Serial version UID.
         */
        private static final long serialVersionUID = 8191965142066368703L;
        /**
         * List of columns.
         */
        private List<Column> columns;

        /**
         * Creates a new <code>ColumnsListModel</code> from given collection
         * of columns.
         *
         * @param columns the columns collection
         */
        public ColumnsListModel(Collection<Column> columns) {
            this.columns = new ArrayList<Column>(columns);
            Collections.sort(this.columns);
        }

        /**
         * Returns the length of the list.
         * 
         * @return the length of the list
         */
        @Override
        public int getSize() {
            return columns.size();
        }

        /**
         * Returns the value at the specified index.
         *
         * @param index the requested index
         * @return the value at <code>index</code>
         */
        @Override
        public Object getElementAt(int index) {
            return columns.get(index);
        }
    }

    /**
     * The class provides table model for choosing logged columns.
     */
    private class LoggedColumnsTableModel extends AbstractTableModel {

        /**
         * Serial version UID.
         */
        private static final long serialVersionUID = -2126867052324355296L;
        /**
         * Column headings.
         */
        private String[] columnHeadings = {"Column name", "Data type", "Log?"};
        /**
         * List of columns in the table.
         */
        private List<Column> columns;
        /**
         * <code>true</code> if the column at targer index is to be logged;
         * <code>false</code> otherwise
         */
        private boolean[] isLogged;

        /**
         * Creates a new <code>LoggedColumnsTableModel</code> for the given
         * table.
         *
         * @param columns collection of table columns
         */
        public LoggedColumnsTableModel(Collection<Column> columns) {
            this.columns = new ArrayList<Column>(columns);
            Collections.sort(this.columns);

            isLogged = new boolean[columns.size()];
            for (int i = 0; i < isLogged.length; ++i) {
                isLogged[i] = this.columns.get(i).getDataType().isLoggeable();
            }
        }

        /**
         * Returns the number of rows in the model.
         *
         * @return the number of rows in the model
         */
        @Override
        public int getRowCount() {
            return columns.size();
        }

        /**
         * Returns the number of columns in the model.
         *
         * @return the number of columns in the model
         */
        @Override
        public int getColumnCount() {
            return columnHeadings.length;
        }

        /**
         * Returns the name of the column at <code>columnIndex</code>.
         *
         * @param columnIndex the index of the column
         * @return the name of the column
         */
        @Override
        public String getColumnName(int columnIndex) {
            return columnHeadings[columnIndex];
        }

        /**
         * Returns the most specific superclass for all the cell values in the
         * column.
         *
         * @param columnIndex the index of the column
         * @return the common ancestor class of the object values in the model
         */
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            switch (columnIndex) {
                case 0:
                case 1:
                    return String.class;
                case 2:
                    return Boolean.class;
                default:
                    assert false;
                    return Object.class;
            }
        }

        /**
         * Returns <code>true</code> if the cell at <code>rowIndex</code> and
         * <code>columnIndex</code> is editable.
         *
         * @param rowIndex the row whose value to be queried
         * @param columnIndex the column whose value to be queried
         * @return <code>true</code> if the cell is editable; <code>false</code>
         * otherwise
         */
        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex) {
            switch (columnIndex) {
                case 0:
                case 1:
                    return false;
                case 2:
                    return true;
                default:
                    assert false;
                    return false;
            }
        }

        /**
         * Returns the value for the cell at <code>rowIndex</code> and
         * <code>columnIndex</code>.
         *
         * @param rowIndex the row whose value to be queried
         * @param columnIndex the column whose value to be queried
         * @return the value Object at the specified cell
         */
        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            switch (columnIndex) {
                case 0:
                    return columns.get(rowIndex).getName();
                case 1:
                    return columns.get(rowIndex).getDataTypeName();
                case 2:
                    return isLogged[rowIndex];
                default:
                    assert false;
                    return null;
            }
        }

        /**
         * Sets the value in the cell at <code>columnIndex</code> and
         * <code>rowIndex</code> to <code>value</code>.
         *
         * @param value the new value
         * @param rowIndex the row whose value is to be changed
         * @param columnIndex the column whose value is to be changed
         */
        @Override
        public void setValueAt(Object value, int rowIndex, int columnIndex) {
            if (!(value instanceof Boolean)) {
                return;
            }

            isLogged[rowIndex] = (Boolean) value;
        }

        /**
         * Returns collection of columns, which are selected to be logged.
         *
         * @return the collection of logged columns
         */
        public Collection<String> getLoggedColumnNames() {
            Set<String> loggedColumnNames = new HashSet<String>();

            for (int columnIndex = 0; columnIndex < columns.size(); ++columnIndex) {
                if (isLogged[columnIndex]) {
                    loggedColumnNames.add(columns.get(columnIndex).getName());
                }
            }

            return loggedColumnNames;
        }
    }
    /**
     * Target table.
     */
    private Table table;
    /**
     * Database object.
     */
    private Database database;
    /**
     * <code>true</code> if the current database supports on update triggers.
     */
    private boolean onUpdateTriggers;
    /**
     * The <code>LoggedColumnsTableModel</code>.
     */
    private LoggedColumnsTableModel loggedColumnsTable;
    /**
     * The <code>ColumnsListModel</code>.
     */
    private ColumnsListModel onUpdateColumnsList;

    /** 
     * Creates a new <code>LoggingSettingsDialog</code>.
     *
     * @param parent the parent frame
     * @param modal <code>true</code> if this dialog is modal
     * @param database the targer database
     * @param table the target table
     * @param onUpdateTriggers <code>true</code> if on update triggers are
     * supported by the current database
     */
    public LoggingSettingsDialog(java.awt.Frame parent, boolean modal,
            Database database, Table table, boolean onUpdateTriggers) {
        super(parent, modal);
        setTitle("Table " + table.getName() + ": Logging settings");

        this.database = database;
        this.table = table;
        this.onUpdateTriggers = onUpdateTriggers;

        loggedColumnsTable = new LoggedColumnsTableModel(table.getNonLoggingColumns());
        onUpdateColumnsList = new ColumnsListModel(table.getNonLoggingColumns());

        initComponents();

        if (!onUpdateTriggers) {
            _onUpdateColumnsPanel.setVisible(false);
        }
    }

    /**
     * Returns triggering events selected by the user.
     *
     * @return triggering events
     */
    public TriggeringEvents getTriggeringEvents() {
        TriggeringEvents events = new TriggeringEvents();

        if (this._insert.isSelected()) {
            events.setEvent(Event.INSERT);
        }
        if (this._update.isSelected()) {
            events.setEvent(Event.UPDATE);
        }
        if (this._delete.isSelected()) {
            events.setEvent(Event.DELETE);
        }


        int selectedColumnsCount = _onUpdateColumns.getSelectedValues().length;
        if (selectedColumnsCount > 0 &&
                selectedColumnsCount < onUpdateColumnsList.getSize()) {
            Set<String> onUpdateColumnNames = new HashSet<String>();
            for (Object o : _onUpdateColumns.getSelectedValues()) {
                onUpdateColumnNames.add(((Column) o).getName());
            }

            events.setOnUpdateColumns(onUpdateColumnNames);
        }

        return events;
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        _triggeringEventsPanel = new javax.swing.JPanel();
        _insert = new javax.swing.JCheckBox();
        _update = new javax.swing.JCheckBox();
        _delete = new javax.swing.JCheckBox();
        _columnsTableScrollPane = new javax.swing.JScrollPane();
        _columnsTable = new javax.swing.JTable();
        _okButton = new javax.swing.JButton();
        _cancelButton = new javax.swing.JButton();
        _onUpdateColumnsPanel = new javax.swing.JPanel();
        _onUpdateColumnsScrollPane = new javax.swing.JScrollPane();
        _onUpdateColumns = new javax.swing.JList();
        jLabel1 = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);

        _triggeringEventsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("Triggering events"));

        _insert.setSelected(true);
        _insert.setText("Insert");

        _update.setSelected(true);
        _update.setText("Update");

        _delete.setSelected(true);
        _delete.setText("Delete");

        javax.swing.GroupLayout _triggeringEventsPanelLayout = new javax.swing.GroupLayout(_triggeringEventsPanel);
        _triggeringEventsPanel.setLayout(_triggeringEventsPanelLayout);
        _triggeringEventsPanelLayout.setHorizontalGroup(
            _triggeringEventsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(_triggeringEventsPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(_triggeringEventsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(_insert)
                    .addComponent(_update)
                    .addComponent(_delete))
                .addContainerGap(44, Short.MAX_VALUE))
        );
        _triggeringEventsPanelLayout.setVerticalGroup(
            _triggeringEventsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(_triggeringEventsPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(_insert)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(_update)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(_delete)
                .addContainerGap(22, Short.MAX_VALUE))
        );

        _columnsTable.setModel(loggedColumnsTable);
        _columnsTableScrollPane.setViewportView(_columnsTable);

        _okButton.setText("OK");
        _okButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                _okButtonActionPerformed(evt);
            }
        });

        _cancelButton.setText("Cancel");
        _cancelButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                _cancelButtonActionPerformed(evt);
            }
        });

        _onUpdateColumnsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder("On update columns"));

        _onUpdateColumns.setModel(onUpdateColumnsList);
        _onUpdateColumnsScrollPane.setViewportView(_onUpdateColumns);

        javax.swing.GroupLayout _onUpdateColumnsPanelLayout = new javax.swing.GroupLayout(_onUpdateColumnsPanel);
        _onUpdateColumnsPanel.setLayout(_onUpdateColumnsPanelLayout);
        _onUpdateColumnsPanelLayout.setHorizontalGroup(
            _onUpdateColumnsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(_onUpdateColumnsPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(_onUpdateColumnsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 181, Short.MAX_VALUE)
                .addContainerGap())
        );
        _onUpdateColumnsPanelLayout.setVerticalGroup(
            _onUpdateColumnsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(_onUpdateColumnsPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(_onUpdateColumnsScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 82, Short.MAX_VALUE)
                .addContainerGap())
        );

        jLabel1.setFont(new java.awt.Font("Tahoma", 1, 11));
        jLabel1.setText("Columns");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addContainerGap()
                        .addComponent(_columnsTableScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 354, Short.MAX_VALUE))
                    .addGroup(layout.createSequentialGroup()
                        .addGap(10, 10, 10)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addGap(10, 10, 10)
                                .addComponent(jLabel1))
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(_triggeringEventsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addGap(18, 18, 18)
                                .addComponent(_onUpdateColumnsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap(128, Short.MAX_VALUE)
                .addComponent(_okButton)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(_cancelButton)
                .addContainerGap(128, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(_onUpdateColumnsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(_triggeringEventsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(jLabel1)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 8, Short.MAX_VALUE)
                .addComponent(_columnsTableScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(18, 18, 18)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(_cancelButton)
                    .addComponent(_okButton))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    /**
     * Cancels creating the logging trigger.
     *
     * @param evt action event
     */
    private void _cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event__cancelButtonActionPerformed
        dispose();
    }//GEN-LAST:event__cancelButtonActionPerformed

    /**
     * Confirms the creating of the logging trigger.
     * 
     * @param evt action event
     */
    private void _okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event__okButtonActionPerformed
        try {
            database.getConnection().createLoggingTrigger(
                    table.getName(), getTriggeringEvents(),
                    loggedColumnsTable.getLoggedColumnNames());

            // refreshes all
            Gui.getInstance().refresh();
        } catch (Exception ex) {
            Dialog.error(ex.getMessage());
        }

        dispose();
    }//GEN-LAST:event__okButtonActionPerformed
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton _cancelButton;
    private javax.swing.JTable _columnsTable;
    private javax.swing.JScrollPane _columnsTableScrollPane;
    private javax.swing.JCheckBox _delete;
    private javax.swing.JCheckBox _insert;
    private javax.swing.JButton _okButton;
    private javax.swing.JList _onUpdateColumns;
    private javax.swing.JPanel _onUpdateColumnsPanel;
    private javax.swing.JScrollPane _onUpdateColumnsScrollPane;
    private javax.swing.JPanel _triggeringEventsPanel;
    private javax.swing.JCheckBox _update;
    private javax.swing.JLabel jLabel1;
    // End of variables declaration//GEN-END:variables
}
